package de.schmidke.palmreader;

import java.io.DataInput;
import java.io.IOException;
import java.util.logging.Logger;

public abstract class Readable {
	private final Logger logger = Logger.getLogger(Readable.class.getName());

	public static class Blob {
		private final boolean hasData;

		public Blob(boolean hasData) {
			this.hasData = hasData;
		}

		public boolean hasData() {
			return hasData;
		}

		@Override
		public String toString() {
			return hasData ? "with data" : "empty";
		}
	}

	protected DataInput is;

	protected Readable() {
	}

	public void load(DataInput is) throws IOException {
		logger.fine("loading " + getClass().getCanonicalName());
		this.is = is;
		try {
			load();
		} finally {
			this.is = null;
		}
	}

	protected abstract void load() throws IOException;

	protected void skipBytes(int count) throws IOException {
		while (count > 0) {
			count -= is.skipBytes(count);
		}
	}

	protected void readUntilFindByteWithValue(int value) throws IOException {
		int ch;
		do {
			ch = is.readUnsignedByte();
		} while (ch != value);
	}

	protected String readString() throws IOException {
		int ln = is.readUnsignedByte();
		if (ln == 0) {
			return "";
		} else if (ln == 255) {
			ln = readUnsignedShort();
		}
		byte[] bytes = new byte[ln];
		is.readFully(bytes);
		return new String(bytes, "ISO-8859-1");
	}

	protected int readInt() throws IOException {
		int ch4 = is.readUnsignedByte();
		int ch3 = is.readUnsignedByte();
		int ch2 = is.readUnsignedByte();
		int ch1 = is.readUnsignedByte();
		return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
	}

	protected int readUnsignedShort() throws IOException {
		int ch2 = is.readUnsignedByte();
		int ch1 = is.readUnsignedByte();
		return (ch1 << 8) + (ch2 << 0);
	}

	private void checkT(int t) throws IOException {
		int tist = readInt();
		if (tist != t) {
			throw new IllegalArgumentException("Typ erwartet=" + t
					+ "; gefunden=" + tist);
		}
	}

	protected int readTypedInt() throws IOException {
		int tist = readInt();
		if (tist != 1 && tist != 2) {
			throw new IllegalArgumentException("Typ erwartet=(1,2)"
					+ "; gefunden=" + tist);
		}
		return readInt();
	}

	protected String readTypedString() throws IOException {
		checkT(5);
		checkT(0);
		return readString();
	}

	protected boolean readTypedBool() throws IOException {
		checkT(6);
		return readInt() != 0;
	}

	protected Object readProperty() throws IOException {
		int value;
		int typeId = readInt();
		switch (typeId) {
		case 1:
		case 6:
		case 7:
		case 64:
		case 66:
			return Integer.valueOf(readInt());
		case 65: {
			// An indicator of binary data, like an image.
			// A value of 0 indicates no data. A value of 1
			// indicates data. No idea how to skip the data.
			value = readInt();
			// Update 20090628 MS
			// Did a little investigation of data after the 41 00 00 00 01 00 00
			// 00
			// Following seems to work to skip binary data
			if (value != 0) {
				skipBytes(8);
				int w = readInt();
				skipBytes(w);
			}
			return new Blob(false);
		}
		case 68:
			value = readInt();
			readInt();
			return Integer.valueOf(value);
		case 5:
			int padding = readInt();
			if (padding != 0) {
				throw new RuntimeException("Unexpected padding value: "
						+ padding);
			}
			return readString();
		case 0:
		default:
			throw new RuntimeException("Unexpected type ID: " + typeId);
		}
	}
}
